﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Mime
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    using Phuse.IO;

    /// <summary>Implements the common functionality for all <see cref="Body"/> subclasses with binary content.
    /// </summary>
    /// <typeparam name="TMostDerived">The most-derived subclass of this type.</typeparam>
    /// <threadsafety static="true" instance="false"/>
    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "MemoryStream does not need to be disposed.")]
    public abstract class BinaryBody<TMostDerived> : Body<TMostDerived>
        where TMostDerived : BinaryBody<TMostDerived>
    {
        private readonly string mediaType;
        private MemoryStream content = new MemoryStream();

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Gets a copy of the body content.</summary>
        /// <remarks>The default value is an empty array, which is set during construction and when the parser does not
        /// find a valid body.</remarks>
        public byte[] GetContent()
        {
            return this.content.ToArray();
        }

        /// <summary>Sets the body content.</summary>
        /// <param name="value">A Stream containing the new content.</param>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> equals <c>null</c>.</exception>
        /// <remarks>
        /// <para>The passed <see cref="MemoryStream"/> must not be modified after calling this method.</para>
        /// <para>This method and <see cref="GetContent"/> are not coalesced into a property for performance reasons.
        /// Calling code is expected to not normally know the length of the content upfront. Instead, content is often
        /// assembled in a <see cref="MemoryStream"/>. Forcing the caller to convert it to an array of bytes before
        /// setting would thus incurr one unnecessary copy of the content.</para>
        /// </remarks>
        public void SetContent(MemoryStream value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            this.content = value;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal BinaryBody(string mediaType, string subtype, FallbackReason fallbackReason) :
            base(subtype, fallbackReason)
        {
            this.mediaType = mediaType;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="BinaryBody{TMostDerived}"/> class.</summary>
        /// <param name="mediaType">The media type of the body.</param>
        /// <param name="subtype">The subtype of the body.</param>
        /// <exception cref="ArgumentException"><paramref name="mediaType"/> and/or <paramref name="subtype"/> is
        /// <c>null</c> or empty.</exception>
        protected BinaryBody(string mediaType, string subtype) : base(subtype)
        {
            if (string.IsNullOrEmpty(mediaType))
            {
                throw new ArgumentException("Cannot be null or empty.", "mediaType");
            }

            this.mediaType = mediaType;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Body.WriteTo(Stream, Action{IEnumerable{HeaderField}, bool})"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Not callable from external code, CA bug?")]
        protected internal sealed override void WriteTo(
            Stream stream, Action<IEnumerable<HeaderField>, bool> writeHeaderFieldsCallback)
        {
            var fields = this.GetRequiredHeaderFields(
                this.mediaType, new KeyValuePair<string, string>[0], ContentTransferEncodingNames.Base64);
            writeHeaderFieldsCallback(fields, true);

            if (stream != null)
            {
                using (var base64Stream = new Base64Stream(new ForwardingStream(stream)))
                {
                    this.content.WriteTo(base64Stream);
                }
            }
        }
    }
}
